﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Controls.Primitives;

namespace LaTeXGrapher
{
    using StreamWriter = System.IO.StreamWriter;
    using Microsoft.Win32;
    using System.Collections;
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public delegate double Function(double x);
        
        public Window1()
        {
            InitializeComponent();
        }

        static Function PointSlope(double x, double y, double m)
        {
            return t => m * (t - x) + y;
        }

        static Function LineFromTwoPoints(Point a, Point b)
        {
            double m = (a.Y - b.Y)/(a.X - b.X);
            return x => m*(x - a.X) + a.Y;
        }

        static Function Secant(Function f, double x1, double x2)
        {
            return LineFromTwoPoints(new Point(x1, f(x1)), new Point(x2, f(x2)));
        }

        //IFunction2d _func = new FunctionInt();//new FunctionAxes();
        IFunction2d _func = new FunctionJ();
        List<Function> _additionalFunctions = new List<Function>();
        FunctionContext _context = null;

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            canvas.Children.Clear();

            Rect rc = new Rect(0, 0, canvas.ActualWidth, canvas.ActualHeight);

            DeviceArrowWidth = 8;
            DeviceTickWidth = 7;
            DeviceScaleY = -1;
            DeviceFontOffset = new Point(0, 0);

            _additionalFunctions.Clear();
            
            if (OpenFile())
            {
                var tm = CreateElements(canvas.Children, rc, _context);

                CreateMarkupElements(tm, canvas.Children, _context);

                canvas.ClipToBounds = true;
            }
        }

        public double DeviceArrowWidth { get; set; }
        public double DeviceTickWidth { get; set; }
        public double DeviceScaleY { get; set; }
        public Point DeviceFontOffset { get; set; }

        private void CreateMarkupElements(Transform tm, IList elements, FunctionContext context)
        {
            foreach (var line in context.Lines)
            {
                var l = line.GetLine(context);
                if (l.HasValue)
                    CreateLineElements(elements, tm, l.Value);
            }

            double arrowWidth = GetArrowWidth(tm, DeviceArrowWidth);
            foreach (var point in context.Points)
                CreatePointElements(elements, tm, point.GetPoints(context), arrowWidth);
        }

        private Transform CreateElements(IList elements, Rect rc, FunctionContext context)
        {
            Point ptMin = context.Min;
            Point ptMax = context.Max;
            TransformGroup tm = MakeViewTransform(rc, ptMin, ptMax);

            Brush brush = new SolidColorBrush(Colors.Black);

            Point min = tm.Transform(ptMin);
            Point max = tm.Transform(ptMax);
            Point center = tm.Transform(new Point(0, 0));

            // add x-axis
            elements.Add(new Line()
            {
                X1 = min.X,
                Y1 = center.Y,
                X2 = max.X,
                Y2 = center.Y,
                Stroke = brush
            });

            double tickWidth = DeviceTickWidth;
            // x ticks
            double t = Math.Ceiling(ptMin.X);
            if (Math.Floor(context.TickInterval.X) - context.TickInterval.X < -0.001)
                t = Math.Ceiling(ptMin.X / context.TickInterval.X) * context.TickInterval.X;
            for (; t <= Math.Floor(ptMax.X); t += context.TickInterval.X)
            {
                Point pt = tm.Transform(new Point(t, 0));
                elements.Add(new Line()
                {
                    X1 = pt.X,
                    Y1 = pt.Y - tickWidth / 2.0,
                    X2 = pt.X,
                    Y2 = pt.Y + tickWidth / 2.0,
                    Stroke = brush
                });

                Vector? vMark = context.MarkTick(true, t, tickWidth);
                if (vMark.HasValue)
                {
                    TextBlock block = new TextBlock(new Run(t.ToString()));
                    block.SetValue(Canvas.LeftProperty, pt.X + vMark.Value.X + DeviceFontOffset.X);
                    block.SetValue(Canvas.BottomProperty, pt.Y + DeviceScaleY * vMark.Value.Y + DeviceFontOffset.Y);
                    elements.Add(block);
                }
            }

            // add y-axis
            elements.Add(new Line()
            {
                X1 = center.X,
                Y1 = min.Y,
                X2 = center.X,
                Y2 = max.Y,
                Stroke = brush
            });

            // y ticks
            for (t = Math.Ceiling(ptMin.Y); t <= Math.Floor(ptMax.Y); t += context.TickInterval.Y)
            {
                Point pt = tm.Transform(new Point(0, t));
                elements.Add(new Line()
                {
                    X1 = pt.X - tickWidth / 2.0,
                    Y1 = pt.Y,
                    X2 = pt.X + tickWidth / 2.0,
                    Y2 = pt.Y,
                    Stroke = brush
                });

                t *= DeviceScaleY;

                Vector? vMark = context.MarkTick(false, t, tickWidth);
                if (vMark.HasValue)
                {
                    TextBlock block = new TextBlock(new Run(t.ToString()));
                    block.SetValue(Canvas.LeftProperty, pt.X + vMark.Value.X + DeviceFontOffset.X);
                    block.SetValue(Canvas.BottomProperty, pt.Y + DeviceScaleY * vMark.Value.Y + DeviceFontOffset.Y);
                    elements.Add(block);
                }

                t /= DeviceScaleY;
            }

            var func = context.PlotFunctions.FirstOrDefault();
            
            // function
            if (func != null && !(func is FunctionAxes))
            {
                string name = "";
                if (func is DynamicFunction)
                    name = (func as DynamicFunction).Name;
                List<Shape> shapes = CreateFunctionElements(ptMin, ptMax, func.Function, func.SpecialPoints, tm,
                    context.Shades.Where(s => s.Name == name).Select(s => s.GetShadeDomain(context)));
                shapes.ForEach(s => { s.Stroke = Brushes.Black; elements.Add(s); });

                var additional = context.PlotFunctions.Skip(1).Select(f => (Function)f.Function);

                foreach (var f in additional)
                {
                    shapes = CreateFunctionElements(ptMin, ptMax, f, new double[] { }, tm, new KeyValuePair<double, double>[] { });
                    shapes.ForEach(s => { s.Stroke = Brushes.Black; elements.Add(s); });
                }
            }

            return tm;
        }

        public void CreateLineElements(IList elements, Transform tm, KeyValuePair<Point,Point> line)
        {
            Brush brush = new SolidColorBrush(Colors.Black);

            Point a = tm.Transform(line.Key);
            Point b = tm.Transform(line.Value);
            elements.Add(new Line()
            {
                X1 = a.X,
                Y1 = a.Y,
                X2 = b.X,
                Y2 = b.Y,
                Stroke = brush
            });
        }


        public void CreatePointElements(IList elements, Transform tm, IEnumerable<Point> points, double width)
        {
            List<Shape> shapes = new List<Shape>();
            foreach (var pt in points)
                AddFilledCircle(shapes, width, pt, tm);
            shapes.ForEach(s => elements.Add(s));
        }

        static double GetArrowWidth(Transform tm, double deviceArrowWidth)
        {
            return tm.Inverse.Transform(new Point(deviceArrowWidth, 0)).X - tm.Inverse.Transform(new Point(0, 0)).X;
        }

        public List<Shape> CreateFunctionElements(Point ptMin, Point ptMax, Function func, IEnumerable<double> special, Transform tm, IEnumerable<KeyValuePair<double,double>> shadeRanges)
        {
            List<KeyValuePair<double, double>> ranges = new List<KeyValuePair<double, double>>(shadeRanges);
            List<KeyValuePair<double, PathSegmentCollection>> activeFills = new List<KeyValuePair<double, PathSegmentCollection>>();

            Dictionary<PathSegmentCollection, Point> fillStarts = new Dictionary<PathSegmentCollection, Point>();
            List<Shape> fills = new List<Shape>();
            List<Shape> shapes = new List<Shape>();

            PathSegmentCollection segments = new PathSegmentCollection();
            double delta = (ptMax.X - ptMin.X) / 200;

            double arrowWidth = GetArrowWidth(tm, DeviceArrowWidth);
            
            double y = func(ptMin.X);
            Point start = new Point(ptMin.X, y);

            PathFigureCollection figures = new PathFigureCollection();

            IEnumerator<double> eSpec = special.GetEnumerator();
            double currentSpecial = eSpec.MoveNext() ? eSpec.Current : double.MaxValue;
            Point ptLast = start;
            bool bLastWasIn = y < ptMax.Y && y > ptMin.Y;
            bool bFirst = true;
            for (double x = ptMin.X + delta; x < ptMax.X; x += delta)
            {
                y = func(x);
                Point pt = new Point(x, y);

                for (int i = 0; i < ranges.Count; i++)
                {
                    if (x > ranges[i].Key)
                    {
                        var segs = new PathSegmentCollection();
                        segs.Add(new LineSegment(pt, false));
                        fillStarts.Add(segs, new Point(x, 0));
                        activeFills.Add(new KeyValuePair<double, PathSegmentCollection>(ranges[i].Value, segs));

                        ranges.RemoveAt(i);
                        i--;
                    }
                }

                for (int i = 0; i < activeFills.Count; i++)
                {
                    if (x > activeFills[i].Key)
                    {
                        var segs = activeFills[i].Value;
                        segs.Add(new LineSegment(pt, false));
                        segs.Add(new LineSegment(new Point(x, 0), false));

                        fills.Add(MakeFillPath(fillStarts[segs], segs, tm, FillBrush));

                        activeFills.RemoveAt(i);
                        i--;
                    }
                }
                
                if (x > currentSpecial) // the special point happens in this section
                {
                    double ln = func(currentSpecial - 1.0e-12); // limit from neg direction
                    double ySpecial = func(currentSpecial);
                    double lp = func(currentSpecial + 1.0e-12); // limit from positive direction

                    bool isNan = double.IsNaN(ySpecial);
                    bool bDn = isNan || Math.Abs(ln - ySpecial) > 1.0e-6; // discontinuity with neg limit
                    bool bDp = isNan || Math.Abs(lp - ySpecial) > 1.0e-6; // discontinuity with pos limit
                    bool bRem = bDn && bDp && Math.Abs(ln - lp) < 1.0e-6; // the discontinuity is removeable

                    segments.Add(new LineSegment(new Point(currentSpecial, ln), true)); // we ignore if we are out as special points should be in (responsibility of user)
                    if (bRem)
                    {
                        AddFilledCircle(shapes, arrowWidth, new Point(currentSpecial, ySpecial), tm);
                        pt = AddCircle(segments, arrowWidth, ptLast, new Point(currentSpecial, (ln + lp) / 2), bLastWasIn);
                    }
                    else if (bDn)
                    {
                        AddCircle(segments, arrowWidth, ptLast, new Point(currentSpecial, ln), bLastWasIn);
                        pt = new Point(currentSpecial, ySpecial);
                        AddFilledCircle(shapes, arrowWidth, pt, tm);
                    }
                    else if (bDp)
                    {
                        AddFilledCircle(shapes, arrowWidth, new Point(currentSpecial, ySpecial), tm);
                        pt = new Point(currentSpecial, lp);
                        segments.Add(new LineSegment(ptLast, false));
                        ptLast = pt;
                        x = pt.X + arrowWidth;
                        y = func(x);
                        pt = new Point(x, y);
                        pt = AddCircle(segments, arrowWidth, ptLast, pt, false);
                    }
                    else
                    {
                        AddFilledCircle(shapes, arrowWidth, new Point(currentSpecial, ySpecial), tm);
                    }

                    currentSpecial = eSpec.MoveNext() ? eSpec.Current : double.MaxValue;

                    x = pt.X + delta;
                    y = func(x);
                    pt = new Point(x, y);
                    if (!bRem)
                        segments.Add(new LineSegment(pt, false)); // we ignore if we are out as special points should be in (responsibility of user)
                }
                
                bool bIn = y < ptMax.Y && y > ptMin.Y;
                segments.Add(new LineSegment(pt, bIn && bLastWasIn));
                foreach (var fill in activeFills)
                    fill.Value.Add(new LineSegment(pt, false));
                if (bIn != bLastWasIn) // add an arrowhead
                {
                    AddArrow(segments, arrowWidth, pt, ptLast, bLastWasIn);
                }
                else if (bFirst)
                {
                    double xPrev = x - 1.0e-6;
                    Point ptPrev = new Point(xPrev, func(xPrev));
                    AddArrow(segments, arrowWidth, pt, ptPrev, false);
                }
                else if (bIn && (x + delta >= ptMax.X))
                {
                    AddArrow(segments, arrowWidth, pt, ptLast, true);
                }
                ptLast = pt;
                bLastWasIn = bIn;
                bFirst = false;
            }

            PathFigure figure = new PathFigure(start, segments, false);
            figures.Add(figure);
            PathGeometry geometry = new PathGeometry(figures);
            geometry.Transform = tm;
            Path path = new Path();
            path.SnapsToDevicePixels = true;
            path.Data = geometry;

            shapes.AddRange(fills);
            shapes.Add(path);

            return shapes;
        }

        Brush _fillBrush;
        Brush FillBrush
        {
            get
            {
                if (_fillBrush == null)
                    _fillBrush = new SolidColorBrush(Color.FromArgb(50, 0, 0, 0));
                return _fillBrush;
            }
        }

        Path MakeFillPath(Point start, PathSegmentCollection segments, Transform tm, Brush brush)
        {
            PathFigure figure = new PathFigure(start, segments, true);
            var figures = new PathFigureCollection();
            figures.Add(figure);
            PathGeometry geometry = new PathGeometry(figures);
            geometry.Transform = tm;
            Path path = new Path();
            path.Fill = brush;
            path.SnapsToDevicePixels = true;
            path.Data = geometry;
            return path;
        }

        private void AddFilledCircle(List<Shape> shapes, double width, Point pt, Transform tm)
        {
            Vector v = new Vector(width / 2, 0);
            PathFigureCollection figures = new PathFigureCollection();
            figures.Add(new PathFigure(pt + v, new PathSegment[]
                {
                    new ArcSegment(pt - v, new Size(width / 2, width / 2), 0, true, SweepDirection.Counterclockwise, true),
                    new ArcSegment(pt + v, new Size(width / 2, width / 2), 0, true, SweepDirection.Counterclockwise, true)
                }, true));
            PathGeometry geometry = new PathGeometry(figures, FillRule.Nonzero, tm);
            Path path = new Path();
            path.Data = geometry;
            path.Fill = Brushes.Black;

            shapes.Add(path);
        }

        private Point AddCircle(PathSegmentCollection segments, double width, Point pt, Point ptLast, bool bLastWasIn)
        {
            Vector v = (pt - ptLast);
            v.Normalize();

            Point ptEnd = pt;
            if (bLastWasIn)
            {
                ptEnd = ptLast;
            }
            v = -v;

            v *= width / 2;

            while (segments.Count > 0)
            {
                LineSegment seg = (segments[segments.Count - 1] as LineSegment);
                if (seg != null && (seg.Point - ptEnd).Length < width / 2)
                    segments.RemoveAt(segments.Count - 1);
                else if (seg != null)
                {
                    seg.Point = ptEnd - v;
                    break;
                }
            }
            segments.Add(new ArcSegment(ptEnd + v, new Size(width / 2, width / 2), 0, true, SweepDirection.Counterclockwise, true));
            segments.Add(new ArcSegment(ptEnd - v, new Size(width / 2, width / 2), 0, true, SweepDirection.Counterclockwise, true));
            segments.Add(new LineSegment(ptEnd + v, false));
            return ptEnd + v;
        }

        private void AddArrow(PathSegmentCollection segments, double arrowWidth, Point pt, Point ptLast, bool bLastWasIn)
        {
            Vector v = -(pt - ptLast);
            Vector vPerp = new Vector(v.Y, -v.X);
            vPerp.Normalize();
            v.Normalize();

            Point ptEnd = pt;
            if (bLastWasIn)
            {
                ptEnd = ptLast;
                v = -v;
            }

            vPerp *= arrowWidth;
            v *= arrowWidth;

            segments.Add(new LineSegment(ptEnd - v + vPerp / 2.0, false));
            segments.Add(new LineSegment(ptEnd, true));
            segments.Add(new LineSegment(ptEnd - v - vPerp / 2.0, true));
            segments.Add(new LineSegment(ptEnd, false));
        }

        public TransformGroup MakeViewTransform(Rect rc, Point ptMin, Point ptMax)
        {
            TransformGroup tm = new TransformGroup();

            // make this tm such that
            //     (ptMin.x, ptMin.y) |-> (rc.left, rc.bottom)
            //  && (ptMax.x, ptMax.y) |-> (rc.right, rc.top)
            //  but keep the aspect ratio the same :)

            Point ptLB = new Point(rc.Left, rc.Bottom);
            Point ptRT = new Point(rc.Right, rc.Top);
            Vector v = ptMax - ptMin;
            if (v.X == 0 || v.Y == 0 || rc.Width == 0 || rc.Height == 0)
                return tm;

            double dA = (double)rc.Width / (double)rc.Height; // aspect ratio of view area
            double dAMM = v.X / v.Y;                          // aspect ratio of entities
            double dScale = 1.0;

            if (dA < dAMM)
            {// the view area is taller then the pattern, so match the width exactly
                dScale = rc.Width / v.X;
                double dY = (rc.Height / dScale - v.Y) / 2.0;
                ptMin -= new Vector(0, dY);
                ptMax += new Vector(0, dY);
            }
            else
            {// the view area is wider then the pattern, so match the height exactly
                dScale = rc.Height / v.Y;
                double dX = (rc.Width / dScale - v.X) / 2.0;
                ptMin -= new Vector(dX, 0);
                ptMax += new Vector(dX, 0);
            }

            Point center = Center(ptMax, ptMin);
            tm.Children.Add(new TranslateTransform(-center.X, -center.Y));
            tm.Children.Add(new ScaleTransform(dScale, DeviceScaleY*dScale));
            tm.Children.Add(new TranslateTransform(rc.Width / 2.0, rc.Height / 2.0));

            tm.Children.Add(new TranslateTransform(rc.X, rc.Y));

            return tm;
        }

        private Point Center(Point ptMax, Point ptMin)
        {
            return new Point((ptMax.X + ptMin.X) / 2.0, (ptMax.Y + ptMin.Y) / 2.0);
        }

        private void Button_Save(object sender, RoutedEventArgs e)
        {
            SaveFileDialog dlg = new SaveFileDialog();
            dlg.Filter = "EPS file (*.eps)|*.eps";
            dlg.OverwritePrompt = true;
            bool? res = dlg.ShowDialog();
            if (res.HasValue && res.Value)
            {
                SaveEps(dlg.FileName, 3 * 72, 3 * 72, _func, _additionalFunctions, _context);
            }
        }

        #region EPS
        bool _bUsePdfMark = false;
        StreamWriter _swEps;
        void EpsBeginFile(StreamWriter sw, double w, double h)
        {
            _swEps = sw;
            _swEps.WriteLine(@"%!PS-Adobe-3.0 EPSF-3.0");
            _swEps.WriteLine("%%BoundingBox: {0} {1} {2} {3}", 0, 0, w, h);
            _swEps.WriteLine("/Helvetica findfont");
            //_swEps.WriteLine("/Times-Roman-Oblique findfont");
            _swEps.WriteLine("10 scalefont setfont");
        }

        void EpsEndFile()
        {
            _swEps.Close();
            _swEps = null;
            _fEpsLineWidth = -1000;
        }

        double _fEpsLineWidth = -1000;
        void EpsSetLineWidth(double w)
        {
            if (_fEpsLineWidth != w)
            {
                _fEpsLineWidth = w;
                _swEps.WriteLine("{0} setlinewidth", w);
            }
        }

        void EpsFill(double gray)
        {
            EpsSetGray(gray);
            _swEps.WriteLine("fill");
            EpsSetGray(0);
        }

        void EpsSetGray(double gray)
        {
            if (_bUsePdfMark)
                _swEps.WriteLine("[ /ca {0} /SetTransparency pdfmark", gray);
            else
                _swEps.WriteLine(gray + " setgray");
        }

        void EpsNewPath()
        {
            _swEps.WriteLine("newpath");
        }

        void EpsMoveTo(Point pt, Transform tm)
        {
            Point start = tm.Transform(pt);
            _swEps.WriteLine("{0} {1} moveto", start.X, start.Y);
        }

        void EpsArct(Point ptT1, Point ptT2, double r, Transform tm)
        {
            double rT = tm.Transform(new Point(r, 0)).X - tm.Transform(new Point(0, 0)).X;
            EpsArct(tm.Transform(ptT1), tm.Transform(ptT2), rT);
        }

        void EpsArct(Point ptT1, Point ptT2, double r)
        {
            _swEps.WriteLine("{0} {1} {2} {3} {4} arct", ptT1.X, ptT1.Y, ptT2.X, ptT2.Y, r);
        }

        void EpsArc(double w, Point start, Point ptT1, Point ptT2, double r, Transform tm)
        {
            double rT = tm.Transform(new Point(r, 0)).X - tm.Transform(new Point(0, 0)).X;
            EpsArc(w, tm.Transform(start), tm.Transform(ptT1), tm.Transform(ptT2), rT);
        }

        void EpsArc(double w, Point start, Point ptT1, Point ptT2, double r)
        {
            EpsSetLineWidth(w);
            _swEps.WriteLine("{0} {1} moveto", start.X, start.Y);
            EpsArct(ptT1, ptT2, r);
            _swEps.WriteLine("stroke");
        }

        void EpsLine(double w, Point ptA, Point ptB, Transform tm)
        {
            EpsLine(w, tm.Transform(ptA), tm.Transform(ptB));
        }

        void EpsLine(double w, Point ptA, Point ptB)
        {
            EpsSetLineWidth(w);
            _swEps.WriteLine("{0} {1} moveto", ptA.X, ptA.Y);
            _swEps.WriteLine("{0} {1} lineto", ptB.X, ptB.Y);
            _swEps.WriteLine("stroke");
        }

        void EpsPolygon(double fill, IEnumerable<Point> pts, Transform tm)
        {
            EpsPolygon(fill, from p in pts
                             select tm.Transform(p));
        }

        void EpsPolygon(double fill, IEnumerable<Point> pts)
        {
            EpsPoints(pts); 
            _swEps.WriteLine("closepath");
            EpsFill(fill);
        }

        void EpsPolyline(double w, IEnumerable<Point> pts, Transform tm)
        {
            EpsPolyline(w, from p in pts
                           select tm.Transform(p));
        }

        void EpsPolyline(double w, IEnumerable<Point> pts)
        {
            EpsSetLineWidth(w);
            EpsPoints(pts);
            _swEps.WriteLine("stroke");
        }

        void EpsPoints(IEnumerable<Point> pts)
        {
            Point ptStart = pts.First();
            _swEps.WriteLine("{0} {1} moveto", ptStart.X, ptStart.Y);
            foreach (Point pt in pts.Skip(1))
                _swEps.WriteLine("{0} {1} lineto", pt.X, pt.Y);
        }

        void EpsPoint(double r, Point pt)
        {
            _swEps.WriteLine("{0} {1} moveto", pt.X, pt.Y);
            _swEps.WriteLine("{0} {1} {2} 0 360 arc", pt.X, pt.Y, r);
            _swEps.WriteLine("fill");
        }

        void EpsText(string str, double x, double y)
        {
            _swEps.WriteLine("{0} {1} moveto", x, y);
            _swEps.WriteLine("({0}) show", str);
        }

        void EpsText(string str, Point pt)
        {
            EpsText(str, pt.X, pt.Y);
        }

        void SaveEps(string strFilePath, double w, double h, IFunction2d func, IEnumerable<Function> additionalFunctions, FunctionContext context)
        {
            using (StreamWriter sw = new StreamWriter(strFilePath))
            {
                EpsBeginFile(sw, w, h);

                Point ptMin = func.Min;
                Point ptMax = func.Max;

                Rect rc = new Rect(0, 0, w, h);

                DeviceArrowWidth = 4;
                DeviceTickWidth = 3;
                DeviceScaleY = 1;
                DeviceFontOffset = new Point(0, 6);

                List<UIElement> collection = new List<UIElement>();
                Transform tm = CreateElements(collection, rc, _context);

                CreateMarkupElements(tm, collection, _context);

                // Shape fills happen first.
                foreach (UIElement ele in collection)
                {
                    if (ele is Path)
                    {
                        bool bFilled = (ele as Path).Fill != null;
                        if (bFilled && (ele as Path).Fill == FillBrush)
                        {
                            EpsPath(ele as Path, bFilled, 0.9);
                        }
                    }
                }

                foreach (UIElement ele in collection)
                {
                    if (ele is Path)
                    {
                        bool bFilled = (ele as Path).Fill != null;
                        if (bFilled && (ele as Path).Fill == FillBrush)
                            continue;

                        EpsPath(ele as Path, bFilled, 0);
                    }
                    else if (ele is Line)
                    {
                        Line line = ele as Line;
                        EpsLine(0.5, new Point(line.X1, line.Y1), new Point(line.X2, line.Y2));
                    }
                    else if (ele is TextBlock)
                    {
                        string str = ((ele as TextBlock).Inlines.First() as Run).Text;
                        EpsText(str, (double)ele.GetValue(Canvas.LeftProperty), (double)ele.GetValue(Canvas.BottomProperty));
                    }
                }
                EpsEndFile();
            }
        }

        private void EpsPath(Path path, bool filled, double fillBrush)
        {
            PathGeometry geo = path.Data as PathGeometry;
            Transform m = geo.Transform;
            PathFigure fig = geo.Figures[0];
            Point ptLast = fig.StartPoint;
            List<Point> points = new List<Point>();
            points.Add(ptLast);
            bool bLastWasArc = false;
            foreach (PathSegment seg in fig.Segments)
            {
                if (seg is LineSegment)
                {
                    Point pt = (seg as LineSegment).Point;

                    if (seg.IsStroked || filled)
                        points.Add(pt); // accumulate
                    else
                    {
                        if (points.Count > 1)
                            EpsPolyline(1, points, m);
                        points.Clear();
                        points.Add(pt);
                    }

                    bLastWasArc = false;
                    ptLast = pt;
                }
                else if (seg is ArcSegment)
                {
                    ArcSegment arc = seg as ArcSegment;
                    Point pt = arc.Point;

                    if (points.Count > 1)
                        EpsPolyline(1, points, m);
                    points.Clear();
                    points.Add(pt);

                    Vector v = arc.Point - ptLast;
                    Vector vPerp = new Vector(v.Y, -v.X);

                    if (!bLastWasArc)
                    {
                        EpsNewPath();
                        EpsMoveTo(ptLast, m);
                    }

                    //                                EpsArc(0.5, ptLast, ptLast + vPerp / 2, arc.Point - vPerp, v.Length / 2, m);
                    EpsArct(ptLast + vPerp / 2, ptLast + v + vPerp / 2, v.Length / 2, m);
                    EpsArct(ptLast + v + vPerp / 2, arc.Point - vPerp, v.Length / 2, m);

                    if (filled && bLastWasArc)
                        EpsFill(fillBrush);
                    bLastWasArc = true;
                    ptLast = pt;
                }
            }

            if (points.Count > 1)
                EpsPolygon(fillBrush, points, m);
        }
        #endregion // eps

        IEnumerable<char> FileSource(string path)
        {
            return System.IO.File.ReadAllText(path);
        }

        bool OpenFile()
        {
            OpenFileDialog dlg = new OpenFileDialog();
            if (dlg.ShowDialog() ?? false)
            {
                FunctionParser parser = new FunctionParser(FileSource(dlg.FileName));
                parser.Parse();

                _context = new FunctionContext();
                parser.LoadContext(_context);
                return true;
            }
            return false;
        }
    }
}
